#include <cassert>
#include <map>
#include <regex>
#include <set>
#include <string>
#include <vector>

#include <fstream>
#include <iostream>

namespace {

static const std::set<std::string> GetValidSymbolsSet() {
  const std::vector<std::string> valid_symbols = {
      "AA",  "AA0", "AA1", "AA2", "AE",  "AE0", "AE1", "AE2", "AH",  "AH0",
      "AH1", "AH2", "AO",  "AO0", "AO1", "AO2", "AW",  "AW0", "AW1", "AW2",
      "AY",  "AY0", "AY1", "AY2", "B",   "CH",  "D",   "DH",  "EH",  "EH0",
      "EH1", "EH2", "ER",  "ER0", "ER1", "ER2", "EY",  "EY0", "EY1", "EY2",
      "F",   "G",   "HH",  "IH",  "IH0", "IH1", "IH2", "IY",  "IY0", "IY1",
      "IY2", "JH",  "K",   "L",   "M",   "N",   "NG",  "OW",  "OW0", "OW1",
      "OW2", "OY",  "OY0", "OY1", "OY2", "P",   "R",   "S",   "SH",  "T",
      "TH",  "UH",  "UH0", "UH1", "UH2", "UW",  "UW0", "UW1", "UW2", "V",
      "W",   "Y",   "Z",   "ZH"};

  std::set<std::string> symbol_set;

  for (auto &s : valid_symbols) {
    symbol_set.insert(s);
  }

  return symbol_set;
}

// https://stackoverflow.com/questions/236129/the-most-elegant-way-to-iterate-the-words-of-a-string
std::vector<std::string> split(const std::string &text,
                               const std::string &delims) {
  std::vector<std::string> tokens;
  std::size_t start = text.find_first_not_of(delims), end = 0;

  while ((end = text.find_first_of(delims, start)) != std::string::npos) {
    tokens.push_back(text.substr(start, end - start));
    start = text.find_first_not_of(delims, end);
  }
  if (start != std::string::npos) tokens.push_back(text.substr(start));

  return tokens;
}

static std::string GetPronounciation(
    const std::set<std::string> &valid_symbols_set, const std::string &s) {

  assert(s.size() >= 1);

  std::string ss = s;
  ss.pop_back();  // strip
  std::vector<std::string> parts = split(ss, " ");

  for (auto &part : parts) {
    if (!valid_symbols_set.count(part)) {
      return std::string();
    }
  }

  // ' '.join(parts)
  std::string ret;
  for (size_t i = 0; i < parts.size(); i++) {
    ret += parts[i];
    if (i != (parts.size() - 1)) {
      ret += ' ';
    }
  }

  return ret;
}

// CMUDict data. http://www.speech.cs.cmu.edu/cgi-bin/cmudict
static bool ParseCMUDict(const std::string &filename,
                  std::map<std::string, std::vector<std::string>> *cmudict) {
  std::ifstream ifs(filename);
  if (!ifs) {
    return false;
  }

  cmudict->clear();

  std::set<std::string> valid_symbols_set = GetValidSymbolsSet();

  std::regex alt_re("\\([0-9]+\\)");

  std::string line;
  while (std::getline(ifs, line)) {
    if ((line.size() > 0) &&
        (((line[0] >= 'A') && (line[0] <= 'Z')) || (line[0] == '\''))) {
      std::vector<std::string> parts = split(line, "  ");

      if (parts.size() >= 2) {
        std::string word;
        assert(!word.empty());

        std::string pronounciation =
            GetPronounciation(valid_symbols_set, parts[1]);
        if (!pronounciation.empty()) {
          (*cmudict)[word].push_back(pronounciation);
        }
      }
    }
  }

  return true;
}


/*

cleaners.py

'''
Cleaners are transformations that run over the input text at both training and eval time.

Cleaners can be selected by passing a comma-delimited list of cleaner names as the "cleaners"
hyperparameter. Some cleaners are English-specific. You'll typically want to use:
  1. "english_cleaners" for English text
  2. "transliteration_cleaners" for non-English text that can be transliterated to ASCII using
     the Unidecode library (https://pypi.python.org/pypi/Unidecode)
  3. "basic_cleaners" if you do not want to transliterate (in this case, you should also update
     the symbols in symbols.py to match your data).
'''

# Regular expression matching whitespace:
_whitespace_re = re.compile(r'\s+')

# List of (regular expression, replacement) pairs for abbreviations:
_abbreviations = [(re.compile('\\b%s\\.' % x[0], re.IGNORECASE), x[1]) for x in [
  ('mrs', 'misess'),
  ('mr', 'mister'),
  ('dr', 'doctor'),
  ('st', 'saint'),
  ('co', 'company'),
  ('jr', 'junior'),
  ('maj', 'major'),
  ('gen', 'general'),
  ('drs', 'doctors'),
  ('rev', 'reverend'),
  ('lt', 'lieutenant'),
  ('hon', 'honorable'),
  ('sgt', 'sergeant'),
  ('capt', 'captain'),
  ('esq', 'esquire'),
  ('ltd', 'limited'),
  ('col', 'colonel'),
  ('ft', 'fort'),
]]


def expand_abbreviations(text):
  for regex, replacement in _abbreviations:
    text = re.sub(regex, replacement, text)
  return text


def expand_numbers(text):
  return normalize_numbers(text)


def lowercase(text):
  return text.lower()


def collapse_whitespace(text):
  return re.sub(_whitespace_re, ' ', text)


def convert_to_ascii(text):
  return unidecode(text)

def basic_cleaners(text):
  '''Basic pipeline that lowercases and collapses whitespace without transliteration.'''
  text = lowercase(text)
  text = collapse_whitespace(text)
  return text


def transliteration_cleaners(text):
  '''Pipeline for non-English text that transliterates to ASCII.'''
  text = convert_to_ascii(text)
  text = lowercase(text)
  text = collapse_whitespace(text)
  return text


def english_cleaners(text):
  '''Pipeline for English text, including number and abbreviation expansion.'''
  text = convert_to_ascii(text)
  text = lowercase(text)
  text = expand_numbers(text)
  text = expand_abbreviations(text)
  text = collapse_whitespace(text)
  return text




symbols.py

_pad        = '_'
_eos        = '~'
_characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!\'(),-.:;? '

# Prepend "@" to ARPAbet symbols to ensure uniqueness (some are the same as uppercase letters):
_arpabet = ['@' + s for s in cmudict.valid_symbols]

# Export all symbols:
symbols = [_pad, _eos] + list(_characters) + _arpabet

numbers.py

_inflect = inflect.engine()
_comma_number_re = re.compile(r'([0-9][0-9\,]+[0-9])')
_decimal_number_re = re.compile(r'([0-9]+\.[0-9]+)')
_pounds_re = re.compile(r'£([0-9\,]*[0-9]+)')
_dollars_re = re.compile(r'\$([0-9\.\,]*[0-9]+)')
_ordinal_re = re.compile(r'[0-9]+(st|nd|rd|th)')
_number_re = re.compile(r'[0-9]+')

def _remove_commas(m):
  return m.group(1).replace(',', '')


def _expand_decimal_point(m):
  return m.group(1).replace('.', ' point ')


def _expand_dollars(m):
  match = m.group(1)
  parts = match.split('.')
  if len(parts) > 2:
    return match + ' dollars'  # Unexpected format
  dollars = int(parts[0]) if parts[0] else 0
  cents = int(parts[1]) if len(parts) > 1 and parts[1] else 0
  if dollars and cents:
    dollar_unit = 'dollar' if dollars == 1 else 'dollars'
    cent_unit = 'cent' if cents == 1 else 'cents'
    return '%s %s, %s %s' % (dollars, dollar_unit, cents, cent_unit)
  elif dollars:
    dollar_unit = 'dollar' if dollars == 1 else 'dollars'
    return '%s %s' % (dollars, dollar_unit)
  elif cents:
    cent_unit = 'cent' if cents == 1 else 'cents'
    return '%s %s' % (cents, cent_unit)
  else:
    return 'zero dollars'

def _expand_ordinal(m):
  return _inflect.number_to_words(m.group(0))


def _expand_number(m):
  num = int(m.group(0))
  if num > 1000 and num < 3000:
    if num == 2000:
      return 'two thousand'
    elif num > 2000 and num < 2010:
      return 'two thousand ' + _inflect.number_to_words(num % 100)
    elif num % 100 == 0:
      return _inflect.number_to_words(num // 100) + ' hundred'
    else:
      return _inflect.number_to_words(num, andword='', zero='oh', group=2).replace(', ', ' ')
  else:
    return _inflect.number_to_words(num, andword='')


def normalize_numbers(text):
  text = re.sub(_comma_number_re, _remove_commas, text)
  text = re.sub(_pounds_re, r'\1 pounds', text)
  text = re.sub(_dollars_re, _expand_dollars, text)
  text = re.sub(_decimal_number_re, _expand_decimal_point, text)
  text = re.sub(_ordinal_re, _expand_ordinal, text)
  text = re.sub(_number_re, _expand_number, text)
  return text

*/

} // namespace

int main(int argc, char **argv) {
  (void)argc;
  (void)argv;

  std::string cmudict_filename = "";

  std::map<std::string, std::vector<std::string>> cmudict;

  bool ret = ParseCMUDict(cmudict_filename, &cmudict);
  if (!ret) {
    std::cerr << "Failed to load/parse CMU dict file : " << cmudict_filename << std::endl;
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}
